/******************************************************************************
 * Copyright (C) 671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "NodeCpp/Net/TcpSocket.h"
#include <cassert>
#include <cstring>
#include "NodeCpp/Platform.h"
#if defined(PLATFORM_WINDOWS)
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <io.h>
#endif
#include <event2/buffer.h>
#include "NodeCpp/Error.h"
#include "NodeCpp/Net/TcpSocketDelegate.h"
#include "NodeCpp/Net/TcpServer.h"

namespace NodeCpp {
namespace Net{

    TcpSocket::TcpSocket(void)
        : ID_(-1)
        , Sock_(-1)
        , Bev_(NULL)
        , Delegate_(NULL)
        , Host_("")
        , Port_(0)
        , ErrorString_("")
        , ErrorCode_(0)
        , FatalError_(false)
        , Owner_(NULL)
        , Runnable_(NULL)
    {

    }

    TcpSocket::~TcpSocket(void)
    {
        _Unintialize();
    }

    bool TcpSocket::Open(struct event_base *_Base, evutil_socket_t _Sock, struct sockaddr *_SockAddr /* = NULL */, int _AddrLen /* = 0 */)
    {
        NODECPP_ASSERT(_Base && _Sock != -1);
        Sock_ = _Sock;

        do
        {
            /* Get host and port of the socket */
            {
                struct sockaddr_storage _SockStorage;
#if defined(PLATFORM_WINDOWS)
                int _Socklen = sizeof(_SockStorage);
#elif defined(PLATFORM_LINUX)
                socklen_t _Socklen = sizeof(_SockStorage);
#endif
                char _Addrbuf[128];
                void *_Inaddr;
                memset(&_SockStorage, 0, sizeof(_SockStorage));
                if (getpeername(_Sock, (struct sockaddr *)&_SockStorage, &_Socklen)) {
                    break;
                }
                if (_SockStorage.ss_family == AF_INET) {
                    Port_ = ntohs(((struct sockaddr_in*)&_SockStorage)->sin_port);
                    _Inaddr = &((struct sockaddr_in*)&_SockStorage)->sin_addr;
                }
                else if (_SockStorage.ss_family == AF_INET6) {
                    Port_ = ntohs(((struct sockaddr_in6*)&_SockStorage)->sin6_port);
                    _Inaddr = &((struct sockaddr_in6*)&_SockStorage)->sin6_addr;
                }
                else {
                    break;
                }
                Host_ = evutil_inet_ntop(_SockStorage.ss_family, _Inaddr, _Addrbuf,
                    sizeof(_Addrbuf));
            }

            /* Register socket events */
            Bev_ = bufferevent_socket_new(_Base, _Sock, BEV_OPT_CLOSE_ON_FREE);
            if (!Bev_) {
                break;
            }
            bufferevent_setcb(Bev_,
                &TcpSocket::_ReadCallback,
                &TcpSocket::_WriteCallback,
                &TcpSocket::_EventCallback,
                this);
            bufferevent_disable(Bev_, EV_WRITE);
            bufferevent_enable(Bev_, EV_READ);

            return true;
        } while (false);

        return false;
    }

    void TcpSocket::Close(void)
    {
        NODECPP_ASSERT(Owner_ != NULL);
        Owner_->_CloseSocket(this);
    }

    void TcpSocket::Send(const std::uint8_t *_Data, std::size_t _Size)
    {
        if (!FatalError_)
        {
            bufferevent_enable(Bev_, EV_WRITE);
            if (bufferevent_write(Bev_, _Data, _Size) != 0) {
                _OnError();
            }
        }
    }

    std::size_t TcpSocket::GetReceivedSize(void)
    {
        return evbuffer_get_length(bufferevent_get_input(Bev_));
    }

    int TcpSocket::Read(std::uint8_t *_Data, std::size_t _Size)
    {
        return evbuffer_remove(bufferevent_get_input(Bev_), _Data, _Size);
    }

	std::size_t TcpSocket::ReadPeek(std::uint8_t *_Data, std::size_t _Size)
    {
        return evbuffer_copyout(bufferevent_get_input(Bev_), _Data, _Size);
    }

    std::size_t TcpSocket::ReadSkip(std::size_t _Size)
    {
        if (evbuffer_drain(bufferevent_get_input(Bev_), _Size) == 0) {
            return _Size;
        }
        return 0;
    }

    void TcpSocket::EnableReceive(bool _Enable)
    {
        if (_Enable) {
            bufferevent_enable(Bev_, EV_READ);
        }
        else {
            bufferevent_disable(Bev_, EV_READ);
        }
    }

    void TcpSocket::EnableSend(bool _Enable)
    {
        if (_Enable) {
            bufferevent_enable(Bev_, EV_WRITE);
        }
        else {
            bufferevent_disable(Bev_, EV_WRITE);
        }
    }

    void TcpSocket::_Unintialize(void)
    {
        if (Bev_)
        {
            bufferevent_disable(Bev_, EV_READ);
            bufferevent_free(Bev_);
            Bev_ = NULL;
            Sock_ = -1;
        }
    }

    void TcpSocket::_OnError(void)
    {
        if (FatalError_) {
            return;
        }
        FatalError_ = true;
        EnableSend(false);
    }

    void TcpSocket::_ReadCallback(struct bufferevent *, void *_Userptr)
    {
        TcpSocket *_Sock = static_cast<TcpSocket*>(_Userptr);
        NODECPP_ASSERT(_Sock);
        _Sock->Delegate_->OnRead();
    }

    void TcpSocket::_WriteCallback(struct bufferevent *, void *_Userptr)
    {
        TcpSocket *_Sock = static_cast<TcpSocket*>(_Userptr);
        NODECPP_ASSERT(_Sock);

        if (_Sock->FatalError_)
        {
            _Sock->EnableSend(false);
            _Sock->Delegate_->OnWrite();
        }
    }

    void TcpSocket::_EventCallback(struct bufferevent *, short _Events, void *_Userptr)
    {
        TcpSocket *_Sock = static_cast<TcpSocket*>(_Userptr);
        NODECPP_ASSERT(_Sock);

        if (_Events & BEV_EVENT_EOF) {
            _Sock->_OnError();
            _Sock->Delegate_->OnEof();
        }
        else if (_Events & BEV_EVENT_ERROR) {
            _Sock->ErrorCode_ = GetLastNetError();
            NodeCpp::GetErrorString(_Sock->ErrorString_, _Sock->ErrorCode_);
            _Sock->_OnError();
            _Sock->Delegate_->OnError();
        }
        else if (_Events & BEV_EVENT_TIMEOUT) {
            int _Flags = 0;
            if (_Events & BEV_EVENT_READING) {
                _Flags |= EF_READ;
            }
            if (_Events & BEV_EVENT_WRITING) {
                _Flags |= EF_WRITE;
            }
            _Sock->Delegate_->OnTimeout(_Flags);
        }
    }

}}
